package com.clp.inflxudb.spring;

import com.clp.inflxudb.anno.Organization;
import com.clp.inflxudb.anno.Param;
import com.clp.inflxudb.anno.Query;
import com.clp.inflxudb.anno.Write;
import com.clp.inflxudb.core.FluxLib;
import com.clp.inflxudb.core.domain.FluxPoint;
import com.clp.inflxudb.core.enums.FluxKey;
import com.clp.inflxudb.util.FluxTimeUtil;
import com.influxdb.query.FluxColumn;
import com.influxdb.query.FluxRecord;
import com.influxdb.query.FluxTable;
import org.springframework.beans.factory.FactoryBean;

import java.lang.reflect.*;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;

class FluxMapperFactoryBean<T> implements FactoryBean<T> {
    private Class<T> iClass;

    public FluxMapperFactoryBean(Class iClass) {
        this.iClass = iClass;
    }

    @Override
    public T getObject() throws Exception {
        Object proxy = Proxy.newProxyInstance(iClass.getClassLoader(),
                new Class[]{iClass}, new FluxProxyBeanInvocationHandler());
        return (T) proxy;
    }

    @Override
    public Class<?> getObjectType() {
        return iClass;
    }

    public void setiClass(Class iClass) {
        this.iClass = iClass;
    }

    private static class FluxProxyBeanInvocationHandler implements InvocationHandler {
        private static FluxTemplate fluxTemplate = FluxTemplateFactoryBean.getFluxTemplate();

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object obj = null;
            if (method.isAnnotationPresent(Query.class) && method.isAnnotationPresent(Write.class))
                throw new RuntimeException("不能同时定义Query和Write！");
            else if (method.isAnnotationPresent(Query.class)) {
                // 1、获取@Organization注解的 组织名称
                String orgName = getOrgName(method);
                // 2、获取@Query注解 导入库列表
                List<FluxLib> fluxLibs = getFluxLibs(method);
                // 3、获取@Query注解 并返回解析后的字符串结果
                String queryStr = getParsedQueryStr(method, args);
                // 4、整合查询语句
                StringBuilder finalQuerySb = new StringBuilder();
                if (!fluxLibs.isEmpty()) {
                    for (FluxLib fluxLib : fluxLibs) {
                        finalQuerySb.append(fluxLib.getFluxStr()).append("\n");
                    }
                }
                finalQuerySb.append(queryStr);
                // 5、查询
                List<FluxTable> fluxTables = fluxTemplate.query(orgName, finalQuerySb.toString());

                // 6、根据返回值封装查询结果
                Type gReturnType = method.getGenericReturnType();
                if (gReturnType instanceof ParameterizedType) {
                    // 仅支持List
                    if (!List.class.isAssignableFrom(method.getReturnType()))
                        throw new RuntimeException("不支持的泛型类型！");
                    // 如果是参数化类型,则强转
                    ParameterizedType paramType = (ParameterizedType) gReturnType;
                    // 获取实际参数类型数组，比如List<User>，则获取的是数组[User]，Map<User,String> 则获取的是数组[User,String]
                    Class<?> returnElemClass = (Class<?>) paramType.getActualTypeArguments()[0];
                    obj = convertFluxTablesToList(fluxTables, returnElemClass);
                } else {
                    // 不是参数化类型,直接获取返回值类型
                    Class<?> returnClass = method.getReturnType();
                    obj = convertFluxTablesToObject(fluxTables, returnClass);
                }
            } else if (method.isAnnotationPresent(Write.class)) {
                Write writeAnno = method.getAnnotation(Write.class);
                // 1、获取@Organization注解的 组织名称
                String orgName = getOrgName(method);
                // 2、获取bucketName，并判断是否为批量还是单个
                Parameter[] params = method.getParameters();
                Type[] gParamTypes = method.getGenericParameterTypes();
                Map<String, Object> strObjs = new HashMap<>();

                List<FluxPoint> fluxPoints = new ArrayList<>();
                for (int i = 0; i < args.length; i++) {
                    Parameter parameter = params[i];
                    Type gParamType = gParamTypes[i];
                    // 如果是 @Param 注解 声明了
                    if (parameter.isAnnotationPresent(Param.class)) {
                        strObjs.put(parameter.getAnnotation(Param.class).value(), args[i]);
                        continue;
                    }
                    // 如果没有 @Param 声明，判断是否是单个FluxPoint类型，有就处理
                    if (FluxPoint.class.isAssignableFrom(args[i].getClass())) {
                        fluxPoints.add(((FluxPoint) args[i]));
                        continue;
                    }
                    // 如果没有 @Param声明，也不是单个FluxPoint类型，判断是否为参数化类型List并且 参数类型 为FluxPoint的子类 ，有就处理
                    if (gParamType instanceof ParameterizedType) {
                        // 仅支持List
                        if (!List.class.isAssignableFrom(args[i].getClass()))
                            throw new RuntimeException("不支持的泛型类型！");
                        // 如果是参数化类型List<T>,则强转
                        ParameterizedType pParamType = (ParameterizedType) gParamType;
                        Class<?> paramElemClass = (Class<?>) pParamType.getActualTypeArguments()[0];
                        if (!FluxPoint.class.isAssignableFrom(paramElemClass)) {
                            throw new RuntimeException("列表元素未继承FluxPoint！");
                        }
                        fluxPoints.addAll(((List<FluxPoint>) args[i]));
                    }
                }
                // 解析 orgName 和 bucketName
                String bucketName = StringUtil.replacePlaceholderByMap(writeAnno.bucket(), strObjs, '#');
                orgName = StringUtil.replacePlaceholderByMap(orgName, strObjs, '#');
                fluxTemplate.writeBlockingByFluxPoints(orgName, bucketName, fluxPoints, writeAnno.wp());
            } else if (method.getName().equals("toString")) {
                obj = proxy.getClass().getName();
            } else {
                obj = method.invoke(proxy, args);
            }
            return obj;
        }

        /**
         * 获取方法所属类或者方法上的 @Organization信息
         *
         * @param method
         * @return
         */
        private static String getOrgName(Method method) {
            Class<?> clazz = method.getDeclaringClass();
            // 1.1、获取@Organization注解的 组织名称
            String orgName = null;
            if (clazz.isAnnotationPresent(Organization.class)) {
                orgName = clazz.getAnnotation(Organization.class).value();
            }
            if (method.isAnnotationPresent(Organization.class)) {
                orgName = method.getAnnotation(Organization.class).value();
            }
            // 校验
            if (orgName == null) {
                throw new RuntimeException("未指定组织名称！");
            }
            return orgName;
        }

        /**
         * 获取方法上的@ImportLibs信息
         *
         * @param method
         * @return
         */
        private static List<FluxLib> getFluxLibs(Method method) {
            if (method.isAnnotationPresent(Query.class)) {
                return Arrays.asList(method.getAnnotation(Query.class).importLibs());
            }
            return new ArrayList<>();
        }

        private static String getParsedQueryStr(Method method, Object[] args) throws IllegalAccessException {
            Query queryAnno = method.getAnnotation(Query.class);
            if (queryAnno.value() == null || queryAnno.value().isEmpty())
                throw new RuntimeException("未填写Flux查询语句！");
            // 解析
            Map<String, Object> strObjs = new HashMap<>();
            Parameter[] parameters = method.getParameters();
            for (int i = 0; i < parameters.length; i++) {
                // 判断是否为有@Param注解的参数
                if (parameters[i].isAnnotationPresent(Param.class)) {
                    String paramName = parameters[i].getAnnotation(Param.class).value();
                    strObjs.put(paramName, args[i]);
                    continue;
                }

                Field[] fields = args[i].getClass().getDeclaredFields();
                for (Field field : fields) {
                    field.setAccessible(true);
                    strObjs.put(field.getName(), field.get(args[i]));
                }
            }
            return StringUtil.replacePlaceholderByMap(queryAnno.value(), strObjs, '#');
        }

        private static <T> List<T> convertFluxTablesToList(List<FluxTable> fluxTables, Class<T> tClass) {
            List<T> retList = new ArrayList<>();

            // 候选的键名
            List<String> labels = fluxTables.get(0).getColumns()
                    .stream().map(fluxColumn -> fluxColumn.getLabel()).collect(Collectors.toList());

            // 根据键名封装数据对象
            for (FluxTable fluxTable : fluxTables) {
                Instant timeIns = null; // 用于暂存查询结果的_time
                for (FluxRecord record : fluxTable.getRecords()) {
                    try {
                        T tElem = tClass.getConstructor().newInstance();
                        for (String label : labels) {
                            if (label.equals(FluxKey._time)) {
                                timeIns = ((Instant) record.getValueByKey(label));
                            }
                            set(tElem, label, record.getValueByKey(label));
                        }
                        // 判断结果类型是否为FluxPoint的子类，如果是就特殊处理 _time列（因为名称不一样）
                        if (tElem instanceof FluxPoint) {
                            FluxPoint tElemFP = (FluxPoint) tElem;
                            tElemFP.set(tElemFP.getTimestampName(), timeIns);
                        }
                        retList.add(tElem);
                    } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                        e.printStackTrace();
                    }
                }
            }

            return retList;
        }

        private static <T> T convertFluxTablesToObject(List<FluxTable> fluxTables, Class<T> tClass) {
            // 候选的键名
            List<String> labels = fluxTables.get(0).getColumns()
                    .stream().map(FluxColumn::getLabel).collect(Collectors.toList());

            // 根据键名封装数据对象
            T tElem = null;
            try {
                tElem = tClass.getConstructor().newInstance();
                Instant time = null; // 用于暂存查询结果的_time
                for (String label : labels) {
                    FluxRecord record = fluxTables.get(0).getRecords().get(0);
                    if (label.equals(FluxKey._time)) {
                        time = ((Instant) record.getValueByKey(label));
                    }
                    set(tElem, label, record.getValueByKey(label));
                }
                // 判断结果类型是否为FluxPoint的子类，如果是就特殊处理 _time列（因为名称不一样）
                if (tElem instanceof FluxPoint) {
                    FluxPoint tElemFP = (FluxPoint) tElem;
                    tElemFP.set(tElemFP.getMeasurementName(), time);
                }
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                e.printStackTrace();
            }

            return tElem;
        }

        /**
         * 设置属性名对应的值，设置失败返回false
         *
         * @param obj：要设置的对象
         * @param keyStr：对象的成员变量
         * @param valObj：设置的值
         * @return
         */
        private static boolean set(Object obj, String keyStr, Object valObj) {
            if (valObj == null) return false;
            Field field = null;
            try {
                field = obj.getClass().getDeclaredField(keyStr);
                field.setAccessible(true);
                Class<?> fieldClass = field.getType();
                if (fieldClass == valObj.getClass()) {
                    field.set(obj, valObj);
                    return true;
                }
                if (fieldClass == Instant.class) {
                    field.set(obj, FluxTimeUtil.stringToInstant(valObj.toString()));
                    return true;
                }
                if (fieldClass == Byte.class) {
                    field.set(obj, Byte.valueOf(valObj.toString()));
                    return true;
                }
                if (fieldClass == Short.class) {
                    field.set(obj, Short.valueOf(valObj.toString()));
                    return true;
                }
                if (fieldClass == Integer.class) {
                    field.set(obj, Integer.valueOf(valObj.toString()));
                    return true;
                }
                if (fieldClass == Long.class) {
                    field.set(obj, Long.valueOf(valObj.toString()));
                    return true;
                }
                if (fieldClass == Float.class) {
                    field.set(obj, Float.valueOf(valObj.toString()));
                    return true;
                }
                if (fieldClass == Double.class) {
                    field.set(obj, Double.valueOf(valObj.toString()));
                    return true;
                }
                if (fieldClass == String.class) {
                    field.set(obj, valObj.toString());
                    return true;
                }
                return false;
            } catch (NoSuchFieldException | IllegalAccessException e) {
                return false;
            }
        }
    }
}

